using System;
using System.IO;

namespace Mono.Cecil {

	public sealed class AssemblyDefinition : ICustomAttributeProvider, IMetadataTokenProvider {

		AssemblyNameDefinition name;

		ModuleDefinition main;
		ModuleDefinitionCollection modules;
		CustomAttributeCollection custom_attributes;

		public AssemblyNameDefinition Name {
			get { return name; }
			set { name = value; }
		}

		public MetadataToken MetadataToken {
			get { return new MetadataToken (TokenType.Assembly, 1); }
			set { }
		}

		public ModuleDefinitionCollection Modules {
			get {
				if (modules != null)
					return modules;

				if (main.HasImage)
					return modules = main.Read (this, (_, reader) => reader.ReadModules ());

				return modules = new ModuleDefinitionCollection () { main };
			}
		}

		public ModuleDefinition MainModule {
			get { return main; }
			internal set { main = value; }
		}

		public MethodDefinition EntryPoint {
			get { return main.EntryPoint; }
			set { main.EntryPoint = value; }
		}

		public bool HasCustomAttributes {
			get {
				if (custom_attributes != null)
					return custom_attributes.Count > 0;

				if (main.HasImage)
					return main.Read (this, (type, reader) => reader.HasCustomAttributes (type));

				return false;
			}
		}

		public CustomAttributeCollection CustomAttributes {
			get {
				if (custom_attributes != null)
					return custom_attributes;

				if (main.HasImage)
					return custom_attributes = main.Read (this, (type, reader) => reader.ReadCustomAttributes (type));

				return custom_attributes = new CustomAttributeCollection ();
			}
		}

		internal AssemblyDefinition ()
		{
		}

		public static AssemblyDefinition ReadAssembly (string filename)
		{
			return ReadAssembly (ModuleDefinition.ReadModule (filename));
		}

		public static AssemblyDefinition ReadAssembly (string file, ReadingMode mode)
		{
			return ReadAssembly (ModuleDefinition.ReadModule (file, mode));
		}

		public static AssemblyDefinition ReadAssembly (Stream stream)
		{
			return ReadAssembly (ModuleDefinition.ReadModule (stream));
		}

		public static AssemblyDefinition ReadAssembly (Stream stream, ReadingMode mode)
		{
			return ReadAssembly (ModuleDefinition.ReadModule (stream, mode));
		}

		static AssemblyDefinition ReadAssembly (ModuleDefinition module)
		{
			var assembly = module.Assembly;
			if (assembly == null)
				throw new ArgumentException ();

			return assembly;
		}
	}
}
